package pe.edu.pucp.cpnc.mm.manager;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import pe.edu.pucp.cpnc.mm.bean.Building;
import pe.edu.pucp.cpnc.mm.bean.Map;
import pe.edu.pucp.cpnc.mm.bean.Nature;
import pe.edu.pucp.cpnc.mm.bean.Object;
import pe.edu.pucp.cpnc.mm.bean.ObjectDetail;
import pe.edu.pucp.cpnc.mm.bean.Player;
import pe.edu.pucp.cpnc.mm.bean.Tile;
import pe.edu.pucp.cpnc.mm.bean.TileDetail;
import pe.edu.pucp.cpnc.mm.bean.Unit;
import pe.edu.pucp.cpnc.mm.resdb.ManagerDB;


public class CmdManager {
	private static ManagerDB db = new ManagerDB();
        
        //The save state manager is initialized when a map is loaded.
        public static SaveStateManager svm = null;
	
	public static ManagerDB getDb() {
		return db;
	}

	public static void setDb(ManagerDB db) {
		CmdManager.db = db;
	}
	
	public static void addPlayer(int id, int color, int type, String civilization, String name, Map map){
		Player newPlayer;
		
		newPlayer = new Player(id, color, type, civilization, name);
		map.getPlayers().add(newPlayer);		
	}
	
	public static void showPlayers(Map map){
		int sizePlayer = map.getPlayers().size();
		System.out.println();
		for(int i=1; i<sizePlayer; i++){
			System.out.println("ID:" + map.getPlayers().get(i).getId());
			System.out.println("Civilizacion:" + map.getPlayers().get(i).getCivilization());
			System.out.println("Color: "+map.getPlayers().get(i).getColor());
			System.out.println("Jugador:" + map.getPlayers().get(i).getName());
			System.out.println();
		}
	}
	

	/**
	 * Changes a tile (From water to grass, for example), also changes the path.
	 * 
	 * @param posX coordinate X of the tile's position.
	 * @param posY coordinate Y of the tile's position.
	 * @param tile the type of tile to change to.
	 * @param map the map where the tile will be changed.
	 */
	public static void changeTile(int posX, int posY, Tile tile, Map map){
            TileDetail ptrTile;
            //If X and Y are within bounds
            if(posX<map.getSizeX() && posY<map.getSizeY() && posX >= 0 && posY>=0){
                ptrTile = map.getTiles()[posY][posX];
                if(ptrTile.getTile().getPath() != tile.getPath() && ptrTile.getOccupied()==1) return;

                ptrTile.setTile(new Tile(tile));
            }	
	}
        
        public static void changeAllTiles(Tile tile, Map map){
            for(int i=0;i<map.getSizeY();i++){
                for(int j=0; j<map.getSizeX(); j++){
                    changeTile(j,i,tile,map);
                }
            }
            svm.pushState(map);
        }

	
	public static int addObject(int posX, int posY, Object object, Map map, int idPlayer, int orientation){                 
            int flag = object.add(posX,posY,object,map,idPlayer,orientation);
            svm.pushState(map);
            return flag;
	}

	/**
	 * 
	 * 
	 * @param posX coordinate X of the position to add.
	 * @param posY coordinate Y of the position to add.
	 * @param object object to add.
	 * @param map the map where the object will be added.
	 * @param idPlayer the Id of the unit's owner.
	 * @return
	 */
	public static int addObject(int posX, int posY, Object object, Map map, int idPlayer){		
            int flag = object.add(posX,posY,object,map,idPlayer);
            svm.pushState(map);
            return flag;
	}
	
	/**
	 * Adds an object, this variation of the method works for the Nature objects, since they don't require
	 * a player ID.
	 * 
	 * @param posX coordinate X of the position to add.
	 * @param posY coordinate Y of the position to add.
	 * @param object object to add.
	 * @param map the map where the object will be added.
	 * @return
	 */
	public static int addObject(int posX, int posY, Object object, Map map){	
            int flag = object.add(posX,posY,object,map);
            svm.pushState(map);
            return flag;
	}
	
	/**
	 * Searches for an object in the specified position.
	 * 
	 * @param posX Coordinate X of the point in the map
	 * @param posY Coordinate Y of the point in the map
	 * @param map The map where the object is
	 * @return Returns a vector of 3 Integers, detailing the position of the object:
	 * vect[0]:Location in the ArrayList of players
	 * vect[1]:Location in the ArrayList of ObjectDetails, in the list of players
	 * vect[2]:The orientation of the object.
	 */
	public static int[] searchObject(int posX, int posY, Map map){
		int i=0;
		int j=0;
		int sizePlayers = map.getPlayers().size();
		int sizeObjectDetails;
		int index[] = {-1,-1,-1};
		
		for(;i<sizePlayers;i++){
			sizeObjectDetails=map.getPlayers().get(i).getObjectDetails().size();
			j=0;
			for(;j<sizeObjectDetails;j++){
				if(posX == map.getPlayers().get(i).getObjectDetails().get(j).getPosX() && posY == map.getPlayers().get(i).getObjectDetails().get(j).getPosY()){
					index[0] = i;
					index[1] = j;
					index[2]=map.getPlayers().get(i).getObjectDetails().get(j).getOrientation();
					return index;
				}				
			}			
		}		
		return index;
	}
	
	/**
	 * Deletes an object in the map.
	 * 
	 * @param posX coordinate X of the object's position.
	 * @param posY coordinate Y of the object's position.
	 * @param map the map where the object will be deleted.
	 */
	public static void eraseObject(int posX, int posY, Map map){							
		int sX=0;
		int sY=0;
                int truePosX = posX;
                int truePosY = posY;
		TileDetail currTile = map.getTiles()[posY][posX];
                
		//If the vector has -1, the searchObject method didnt find the object
		if(currTile.getObjInside() == null) {
			System.out.println("No existe ningun objeto en esa posicion");
			return;
		}
                
		//Get the object's SizeX and SizeY
		sX = currTile.getObjInside().getObject().getSizeX();
		sY = currTile.getObjInside().getObject().getSizeY();
                
                if(currTile.getIsRoot() == 0){
                    truePosX = currTile.getObjInside().getPosX();
                    truePosY = currTile.getObjInside().getPosY();
                }
		
		//Exchange the object's SizeX and SizeY 
		if(currTile.getObjInside().getOrientation() == 1 || currTile.getObjInside().getOrientation() == 3){//If the orientation is East(1) or West(3)
			int auxSize = sX;
			sX = sY;
			sY = auxSize;
		}
                
                int[] aux = searchObject(truePosX,truePosY, map);
		
		//Set the previously occupied tiles to unoccupied
		for(int m=truePosY;m<truePosY + sY;m++){
			for(int n=truePosX;n<sX + truePosX;n++){
				map.getTiles()[m][n].setOccupied(0);
                                map.getTiles()[m][n].setObjInside(null);
			}
		}
                map.getTiles()[truePosY][truePosX].setIsRoot(0);
		map.getPlayers().get(aux[0]).getObjectDetails().remove(aux[1]);	
                svm.pushState(map);
	}
	
	
	/**
	 * Rotates and object in the map, if its SizeX is different from its SizeY, it will also be adjusted.
	 * 
	 * @param posX coordinate X of the object's position.
	 * @param posY coordinate Y of the object's position.
	 * @param op the direction of the rotation. 1 = Clockwise, 2=Counter clockwise.
	 * @param map the map where the object will be rotated.
         * @return an integer indicating success or error in rotating the object
         *         0 = ok, 1 = no object found, -1 = out of bounds
	 */
	public static int rotateObject(int posX, int posY, int op, Map map){		
            //op1 = Clockwise, op2 = Counter clockwise
            if(posX>map.getSizeX()-1 || posY>map.getSizeY()-1 || posX<0 || posY<0)
                return -1;
            
            ObjectDetail oDet = map.getTiles()[posY][posX].getObjInside();
            
            if(oDet!=null){
                //If it can be rotated in more than eight directions, its size doesnt matter, it must have
                //square proportions.
                if(oDet.getObject().getSprite().size()>4){
                    if(op==1){
                        if(oDet.getOrientation()==oDet.getObject().getSprite().size()-1)
                            oDet.setOrientation(0);
                        else oDet.setOrientation(oDet.getOrientation()+1);
                    }else{
                        if(oDet.getOrientation()==0)
                            oDet.setOrientation(oDet.getObject().getSprite().size()-1);
                        else oDet.setOrientation(oDet.getOrientation()-1);
                    }
                    
                }else{
                    if(oDet.getObject().getSprite().size()<=2){
                        Object auxObj = oDet.getObject();
                        int player = 0;
                        for(Player p : map.getPlayers()){
                            for(ObjectDetail od : p.getObjectDetails()){
                                if(od.equals(oDet)){
                                    player = p.getId();
                                    break;
                                }
                            }
                        }
                        eraseObject(posX,posY,map);
                        int flag;
                        flag = addObject(oDet.getPosX(),oDet.getPosY(),auxObj,map,player, (oDet.getOrientation()+1) % 2);
                        if(flag == -1)
                            addObject(oDet.getPosX(),oDet.getPosY(),auxObj,map,player, oDet.getOrientation());
                    }
                }
                return 0;

            }
            return 1;
	}
	
	/**
	 * Moves an object from a pos1 to a pos2.
	 * 
	 * @param posX coordinate X of the pos1.
	 * @param posY coordinate Y of the pos1.
	 * @param newPosX coordinate X of the pos2.
	 * @param newPosY coordinate Y of the pos2.
	 * @param map the map where the object is being moved.
	 */
	public static void updateObject(int posX, int posY, int newPosX, int newPosY, Map map){
            int aux[];	
            int idPlayer;
            Object auxObj;
            int flag = -1;

            int offsetX = 0;
            int offsetY = 0;



            ObjectDetail currObject = map.getTiles()[posY][posX].getObjInside();
            if(currObject == null) {
                    System.out.println("No existe ningun objeto en esa posicion");
                    return;
            }
            
            if(map.getTiles()[posY][posX].getIsRoot() == 0){ // NO es la raiz del objeto
                offsetX = currObject.getPosX() - posX;
                offsetY = currObject.getPosY() - posY;
            }

            aux = searchObject(posX + offsetX,posY + offsetY,map);

            auxObj = currObject.getObject();
            idPlayer = aux[0];

            eraseObject(posX + offsetX,posY + offsetY, map);

            flag = addObject(newPosX + offsetX,newPosY + offsetY,auxObj,map,idPlayer, aux[2]);

            if(flag == -1){
                addObject(posX + offsetX,posY + offsetY, auxObj,map,idPlayer, aux[2]);
                return;
            }
            
            svm.pushState(map);
	}
	
	
	/**
	 * Converts an XML file to a map object.
	 * 
	 * @param file the route where he file is.
	 * @return returns the map object loaded, returns null if failed.
	 */
	public static Map load(String file){
		try {  
			Map map = new Map();
			JAXBContext jaxbContext = JAXBContext.newInstance(Map.class, Object.class,Building.class,Unit.class,Nature.class, ObjectDetail.class, TileDetail.class);  
			  
			Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();  
			     
			File XMLfile = new File(file);  
			
			map = (Map)jaxbUnmarshaller.unmarshal(XMLfile);  
			return map;
		}catch (UnmarshalException e){
			System.out.println("El archivo no se pudo cargar con exito.");
			return null;
	    }catch(JAXBException e){
			System.out.println("El archivo no se pudo cargar con exito.");
			e.printStackTrace();
			return null;
		}
	}
	
	/**
	 * Converts the received Map object into an XML file
	 * in the specified route.
	 * 
	 * @param map the Map object that will be converted.
	 * @param file the route where the file will be created.
	 */
	public static void save(Map map,String file){
            try {  
                JAXBContext jaxbContext = JAXBContext.newInstance(Map.class, Object.class,Building.class,Unit.class,Nature.class, ObjectDetail.class, TileDetail.class);  
                Marshaller jaxbMarshaller = jaxbContext.createMarshaller();  

                jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);  

                File XMLfile = new File(file);
                jaxbMarshaller.marshal(map, XMLfile); 

                System.out.println("El mapa ha sido grabado con exito en "
                        +file);
                System.out.println();

            } catch (JAXBException e) {  
                    e.printStackTrace();  
            }
	}
	
	/**
	 * Converts an XML file to a ManagerDB object.
	 * 
	 * @param file the route where the file is.
	 */
	public static void loadDB(String file){
            try {  
                JAXBContext jaxbContext = JAXBContext.newInstance(ManagerDB.class);  

                Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();  

                File XMLfile = new File(file);  

                db = (ManagerDB)jaxbUnmarshaller.unmarshal(XMLfile);  

            }catch(JAXBException e){
                System.out.println("No se cargaron correctamente los recursos");
                e.printStackTrace();
            }
	}
	
	/**
	 * Converts the ManagerDB object into an XML file
	 * in the specified route.
	 * 
	 * @param file the route where the file will be created.
	 */
	public static void saveDB(String file){
            try {  
                JAXBContext jaxbContext = JAXBContext.newInstance(ManagerDB.class);  
                Marshaller jaxbMarshaller = jaxbContext.createMarshaller();  

                jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);  

                File XMLfile = new File(file);
                jaxbMarshaller.marshal(db, XMLfile);      

            } catch (JAXBException e) {  
                e.printStackTrace();  
	    }
	}
        
        public static void export(Map map, String name) {
        int sizeX = map.getSizeX();
        int sizeY = map.getSizeY();
        char[][] matrix = new char[sizeY][sizeX];
        for (int i = 0; i < sizeY; i++) {
            for (int j = 0; j < sizeX; j++) {
                matrix[i][j] = 'T';
            }            
        }
        ArrayList<Player> players = map.getPlayers();
        for (int i = 0; i < players.size(); i++) {
            ArrayList<ObjectDetail> objects = players.get(i).getObjectDetails();
            for (int j = 0; j < objects.size(); j++) {
                Object object = objects.get(j).getObject();
                int i1 = objects.get(j).getPosY();
                int j1 = objects.get(j).getPosX();
                int rows = object.getSizeY();
                int cols = object.getSizeX();
                for (int k = i1; k < i1+rows; k++) {                    
                    for (int l = j1; l < j1+cols; l++) {                        
                        matrix[k][l] = 'Z';
                    }
                }                
                rows++; cols++;
                i1--;
                for (int k = 0; k < Math.min(rows-1,cols-1); k++) {
                    for (int l = j1; l < j1+cols; l++)                         
                        if(l<sizeX && i1>=0 && matrix[i1][l]=='T') matrix[i1][l] = k==0?'V':'U';
                    for (int l = i1; l < i1+rows; l++) 
                        if(l>=0 && l<sizeY && j1+cols-1<sizeX && matrix[l][j1+cols-1]=='T') matrix[l][j1+cols-1] = k==0?'V':'U';                      
                    i1--; j1++;
                }
            }
        }
        
        try {            
            File file = new File(name); 
            file.createNewFile();
            FileWriter fw = new FileWriter(file.getAbsoluteFile());
            BufferedWriter bw = new BufferedWriter(fw);
            for (int i = 0; i < sizeY; i++) {
                bw.write(matrix[i]); 
                bw.newLine();
            }      
            bw.close();
            System.out.println("El mapa ha sido grabado con exito en "
                    + name);
            System.out.println();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

